package com.zhoug.zxing3.activity;

import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.google.zxing.Result;
import com.google.zxing.ResultMetadataType;
import com.zhoug.permission3.ZPermissions;
import com.zhoug.zxing3.R;
import com.zhoug.zxing3.camera.CameraManager;
import com.zhoug.zxing3.decode.DecodeThread;
import com.zhoug.zxing3.utils.BeepManager;
import com.zhoug.zxing3.utils.CaptureActivityHandler;
import com.zhoug.zxing3.utils.InactivityTimer;
import com.zhoug.zxing3.utils.Utils;
import com.zhoug.zxing3.utils.ZxingDecoder;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import static com.google.zxing.ResultMetadataType.BYTE_SEGMENTS;

/**
 * 扫码二维码/条形码
 */
public final class ScanCodeActivity extends AppCompatActivity implements SurfaceHolder.Callback, ICaptureActivity {
    private static final String TAG = ">>>ScanCodeActivity";
    public static final String PARAM_TITLE = "title";//标题
    public static final String PARAM_DESCRIPTION = "Description";//标题
    public static final String PARAM_SEARCH_ENABLE = "searchEnable";//是否启用右边的搜索按钮
    public static final String PARAM_CHOOSE_IMAGE_ENABLE = "chooseImageEnable";//是否允许选择相册中的图片识别二维码
    public static final String PARAM_SEARCH_ACTIVITY_NAME = "searchActivityName";//搜索页面Activity全名
    public static final String PARAM_SEARCH_HINT_TEXT = "searchHintText";//搜索提示
    public static final String PARAM_SCAN_RECT_BG_ENABLE = "scanRectBgEnable";//扫描框范围矩形背景是否启用,

    private static final String DEFAULT_TITLE = "扫一扫";
    private static final String DEFAULT_DESCRIPTION = "扫描二维码/条形码";

    private ActivityResultLauncher<Intent> mChooserLauncher;
    private ActivityResultLauncher<Intent> mSearchLauncher;

    private CameraManager cameraManager;
    private CaptureActivityHandler handler;
    private InactivityTimer inactivityTimer;
    private BeepManager beepManager;

    private SurfaceView scanPreview = null;
    private ImageView scanLine;
    private View scanContainer;
    private View backView;
    private View searchView;
    private View topView;
    private TextView titleView;
    private TextView descriptionView;
    private ImageView btnImg;


    private Rect mCropRect = null;
    private boolean isHasSurface = false;
    private boolean mSearchEnable = false;//是否启用右边的搜索按钮
    private boolean mChooseImageEnable = true;//是否允许选择相册中的图片识别二维码
    private String mSearchActivityName;//搜索页面Activity全名
    private String mTitle = DEFAULT_TITLE;//标题
    private String mDescription = DEFAULT_DESCRIPTION;//描述
    private String mSearchHintText;//搜索提示
    /**
     * 扫描框范围矩形背景是否启用
     * true:设置背景为R.drawable.zxing_scan_rect_bg
     * false:设置背景@android:color/transparent
     */
    private boolean mScanRectBgEnable = false;

    private ZxingDecoder zxingDecoder;

    public Handler getHandler() {
        return handler;
    }

    public CameraManager getCameraManager() {
        return cameraManager;
    }

    private void initIntentData() {
        Bundle extras = getIntent().getExtras();
        if (null != extras) {
            try {
                mSearchEnable = extras.getBoolean(PARAM_SEARCH_ENABLE, false);
                mSearchActivityName = extras.getString(PARAM_SEARCH_ACTIVITY_NAME);
                mTitle = extras.getString(PARAM_TITLE, DEFAULT_TITLE);
                mDescription = extras.getString(PARAM_DESCRIPTION, DEFAULT_DESCRIPTION);
                mSearchHintText = extras.getString(PARAM_SEARCH_HINT_TEXT);
                mChooseImageEnable = extras.getBoolean(PARAM_CHOOSE_IMAGE_ENABLE, false);
                mScanRectBgEnable = extras.getBoolean(PARAM_SCAN_RECT_BG_ENABLE, false);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        Window window = getWindow();
        window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        setContentView(R.layout.zxing_activity_scan_code);
        Utils.transparentStatusBar(this);
        initIntentData();

        scanPreview = findViewById(R.id.capture_preview);
        scanLine = findViewById(R.id.capture_scan_line);
        scanContainer = findViewById(R.id.capture_container);
        backView = findViewById(R.id.rl_left);
        titleView = findViewById(R.id.tv_title);
        topView = findViewById(R.id.topView);
        btnImg = findViewById(R.id.btn_img);
        searchView = findViewById(R.id.rl_right);
        descriptionView = findViewById(R.id.tv_description);

        int statusHeight = Utils.getStatusHeight(this);
        if (statusHeight > 0) {
            topView.setPadding(0, statusHeight, 0, 0);
        }
        inactivityTimer = new InactivityTimer(this);
        beepManager = new BeepManager(this);
        titleView.setText(mTitle);
        descriptionView.setText(mDescription);

        //返回
        if (backView != null) {
            backView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    finish();
                }
            });
        }
        if (mSearchEnable) {
            mSearchLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult -> {
                if (activityResult != null && activityResult.getResultCode() == Activity.RESULT_OK) {
                    Intent data = activityResult.getData();
                    String result = null;
                    if (data != null) {
                        result = data.getStringExtra(SearchActivity.PARAM_RESULT);
                    }
                    Intent resultIntent = new Intent();
                    resultIntent.putExtra("result", result);
                    ScanCodeActivity.this.setResult(RESULT_OK, resultIntent);
                    ScanCodeActivity.this.finish();
                }
            });
            searchView.setVisibility(View.VISIBLE);
            searchView.setOnClickListener(v -> {
                if (mSearchActivityName != null) {
                    Intent intent = new Intent();
                    intent.setComponent(new ComponentName(this, mSearchActivityName));
//                    startActivity(intent);
//                    finish();
                    mSearchLauncher.launch(intent);
                } else {
                    Intent intent = new Intent(this, SearchActivity.class);
                    intent.putExtra(SearchActivity.PARAM_SEARCH_HINT_TEXT, mSearchHintText);
                    mSearchLauncher.launch(intent);
                }
            });
        } else {
            searchView.setVisibility(View.GONE);
        }

        if (mScanRectBgEnable) {
            scanContainer.setBackgroundResource(R.drawable.zxing_scan_rect_bg);
        }


        //选择图片
        if (mChooseImageEnable) {
            mChooserLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult -> {
                if (activityResult != null && activityResult.getResultCode() == Activity.RESULT_OK) {
                    Intent data = activityResult.getData();
                    if (data != null) {
                        Uri uri = data.getData();
                        if (uri != null) {
                            decodeFromImage(uri);
                        } else {
                            Log.e(TAG, "onActivityResult:uri=null");
                            Toast.makeText(getActivity(), "获取图片失败", Toast.LENGTH_SHORT).show();
                        }
                    } else {
                        Log.e(TAG, "onActivityResult:data=null");
                        Toast.makeText(getActivity(), "获取图片失败", Toast.LENGTH_SHORT).show();
                    }
                }
            });
            btnImg.setVisibility(View.VISIBLE);
            btnImg.setOnClickListener(v -> ZPermissions.with(this)
                    .setPermissions(Manifest.permission.READ_EXTERNAL_STORAGE)
                    .setDescription("调用相册需要存储权限")
                    .setCallback((success, granted, denied) -> {
                        if (success) {
                            Intent intent = new Intent(Intent.ACTION_PICK);
                            intent.setType("image/*");
                            mChooserLauncher.launch(intent);
                        }
                    })
                    .request());
        } else {
            btnImg.setVisibility(View.GONE);
        }


    }

    private TranslateAnimation mScanAnim;//扫描动画

    /**
     * 扫描动画
     */
    private void startScanAnim() {
        mScanAnim = new TranslateAnimation(
                Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f,
                Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.9f);
        mScanAnim.setDuration(3000);
        mScanAnim.setRepeatCount(-1);
        mScanAnim.setRepeatMode(Animation.RESTART);
        scanLine.startAnimation(mScanAnim);

    }

    @Override
    protected void onResume() {
        super.onResume();

        // CameraManager must be initialized here, not in onCreate(). This is
        // necessary because we don't
        // want to open the camera driver and measure the screen size if we're
        // going to show the help on
        // first launch. That led to bugs where the scanning rectangle was the
        // wrong size and partially
        // off screen.
        cameraManager = new CameraManager(getApplication());

        handler = null;

        if (isHasSurface) {
            // The activity was paused but not stopped, so the surface still
            // exists. Therefore
            // surfaceCreated() won't be called, so init the camera here.
            initCamera(scanPreview.getHolder());
        } else {
            // Install the callback and wait for surfaceCreated() to init the
            // camera.
            scanPreview.getHolder().addCallback(this);
        }

        inactivityTimer.onResume();
        startScanAnim();
    }

    @Override
    protected void onPause() {
        if (handler != null) {
            handler.quitSynchronously();
            handler = null;
        }
        inactivityTimer.onPause();
        beepManager.close();
        cameraManager.closeDriver();
        if (!isHasSurface) {
            scanPreview.getHolder().removeCallback(this);
        }
        if (mScanAnim != null) {
            mScanAnim.cancel();
            mScanAnim = null;
        }
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        inactivityTimer.shutdown();
        if (mChooserLauncher != null) {
            mChooserLauncher.unregister();
        }
        if (mSearchLauncher != null) {
            mSearchLauncher.unregister();
        }
        super.onDestroy();
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        if (holder == null) {
            Log.e(TAG, "*** WARNING *** surfaceCreated() gave us a null surface!");
        }
        if (!isHasSurface) {
            isHasSurface = true;
            initCamera(holder);
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        isHasSurface = false;
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    /**
     * A valid barcode has been found, so give an indication of success and show
     * the results.
     *
     * @param rawResult The contents of the barcode.
     * @param bundle    The extras
     */
    public void handleDecode(Result rawResult, @NonNull Bundle bundle) {
        inactivityTimer.onActivity();
        beepManager.playBeepSoundAndVibrate();
//        Log.d(TAG, "handleDecode:getText="+rawResult.getText());
//        Log.d(TAG, "handleDecode:getNumBits="+rawResult.getNumBits());
//        Log.d(TAG, "handleDecode:getTimestamp="+rawResult.getTimestamp());
//        BarcodeFormat barcodeFormat = rawResult.getBarcodeFormat();
//        Log.d(TAG, "handleDecode:barcodeFormat="+barcodeFormat.name());
//        ResultPoint[] resultPoints = rawResult.getResultPoints();
//        if(resultPoints!=null && resultPoints.length>0){
//            for (ResultPoint item :resultPoints ) {
//                Log.d(TAG, "handleDecode:ResultPoint="+item.toString());
//            }
//        }
        Intent resultIntent = new Intent();
        bundle.putInt("width", mCropRect.width());
        bundle.putInt("height", mCropRect.height());
        if (rawResult != null) {
            bundle.putString("result", rawResult.getText());
            byte[] rawBytes = rawResult.getRawBytes();
            bundle.putByteArray("result_RawBytes", rawBytes);
            try {
                Map<ResultMetadataType, Object> resultMetadata = rawResult.getResultMetadata();
                if (resultMetadata != null && !resultMetadata.isEmpty()) {
                    Iterator<Map.Entry<ResultMetadataType, Object>> iterator = resultMetadata.entrySet().iterator();
                    tag:
                    while (iterator.hasNext()) {
                        Map.Entry<ResultMetadataType, Object> next = iterator.next();
                        ResultMetadataType key = next.getKey();
                        if (key == BYTE_SEGMENTS) {
                            Object value = next.getValue();
//                            Log.d(TAG, "handleDecode:key=" + key);
//                            Log.d(TAG, "handleDecode:value=" + value);
//                            Log.d(TAG, "handleDecode:value.class=" + value.getClass());
                            if (value instanceof ArrayList<?> arrayList) {
                                if (!arrayList.isEmpty()) {
                                    for (Object item : arrayList) {
                                        Log.d(TAG, "handleDecode:item=" + item.getClass());
                                        if (item instanceof byte[] byteArray) {
                                            bundle.putByteArray("byteArray", byteArray);
                                            break tag;

                                        }

                                    }
                                }


                            }
                        }

                    }

                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        resultIntent.putExtras(bundle);
        this.setResult(RESULT_OK, resultIntent);
        ScanCodeActivity.this.finish();
    }

    private void initCamera(SurfaceHolder surfaceHolder) {
        if (surfaceHolder == null) {
            throw new IllegalStateException("No SurfaceHolder provided");
        }
        if (cameraManager.isOpen()) {
            Log.w(TAG, "initCamera() while already open -- late SurfaceView callback?");
            return;
        }
        try {
            cameraManager.openDriver(surfaceHolder);
            // Creating the handler starts the preview, which can also throw a
            // RuntimeException.
            if (handler == null) {
                handler = new CaptureActivityHandler(this, cameraManager, DecodeThread.ALL_MODE);
            }

            initCrop();
        } catch (IOException ioe) {
            Log.w(TAG, ioe);
            displayFrameworkBugMessageAndExit();
        } catch (RuntimeException e) {
            // Barcode Scanner has seen crashes in the wild of this variety:
            // java.?lang.?RuntimeException: Fail to connect to camera service
            Log.w(TAG, "Unexpected error initializing camera", e);
            displayFrameworkBugMessageAndExit();
        }
    }

    private void displayFrameworkBugMessageAndExit() {
        // camera error
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(getString(R.string.zxing_bar_name));
        builder.setMessage("Camera error");
        builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                finish();
            }

        });
        builder.setOnCancelListener(new DialogInterface.OnCancelListener() {

            @Override
            public void onCancel(DialogInterface dialog) {
                finish();
            }
        });
        builder.show();
    }

    public void restartPreviewAfterDelay(long delayMS) {
        if (handler != null) {
            handler.sendEmptyMessageDelayed(R.id.restart_preview, delayMS);
        }
    }

    public Rect getCropRect() {
        return mCropRect;
    }

    /**
     * 初始化截取的矩形区域
     */
    @SuppressWarnings("SuspiciousNameCombination")
    private void initCrop() {
        int cameraWidth = cameraManager.getCameraResolution().y;
        int cameraHeight = cameraManager.getCameraResolution().x;
        /** 生成最终的截取的矩形 */
        mCropRect = new Rect(0, 0, cameraWidth, cameraHeight);

    }


    private void decodeFromImage(Uri uri) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                String pathFromUri = Utils.getPathFromUri(getActivity(), uri);
                if (pathFromUri != null) {
                    Log.d(TAG, "onActivityResult:pathFromUri=" + pathFromUri);
                    if (zxingDecoder == null) {
                        zxingDecoder = new ZxingDecoder();
                    }
                    try {
                        Result result = zxingDecoder.decode(pathFromUri);
                        if (result != null) {
                            runOnUiThread(() -> handleDecode(result, new Bundle()));
                        } else {
                            runOnUiThread(() -> Toast.makeText(getActivity(), "没有找到二维码/条形码", Toast.LENGTH_SHORT).show());
                        }
                    } catch (Exception e) {
                        runOnUiThread(() -> Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_SHORT).show());
                    }

                } else {
                    Log.e(TAG, "onActivityResult:pathFromUri=null");
                    runOnUiThread(() -> Toast.makeText(getActivity(), "获取图片失败", Toast.LENGTH_SHORT).show());
                }
            }
        });
        thread.start();

    }

    private ScanCodeActivity getActivity() {
        return this;
    }


}